/*----------------------------------------------------------------------*
 * IFFR.C  Support routines for reading IFF-85 files.          1/23/86
 * (IFF is Interchange Format File.)
 *
 * By Jerry Morrison and Steve Shaw, Electronic Arts.
 * This software is in the public domain.
 *
 * This version for the Commodore-Amiga computer.
 *
 * Uses "gio".  Either link with gio.c, or set the GIO_ACTIVE flag to 0
 * in gio.h.
 *----------------------------------------------------------------------*/
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "basiciff.h"

typedef int BOOL;
#define TRUE 1
#define FALSE 0

/* ----- Private subroutine FileLength() --------------------------------*/
/* \brief Returns the length of the file or else a negative IFFP error code (NO_FILE or DOS_ERROR).
 *
 * This was a AmigaDOS-specific implementation with SIDE EFFECT: Thanks
 * to AmigaDOS, we have to change the file's position to find its
 * length.  Now if Amiga DOS maintained fh_End, we'd just do this:
 * fileLength = (FileHandle *)BADDR(file)->fh_End;
 *
 * \param file file handle to get file length from
 * \return file length or ToDo!
 */
off_t FileLength(FILE *file) {
  int i;

  if (file != NULL) {
    struct stat mystat;

    i = fstat(fileno(file), &mystat);
    if(i != -1) return mystat.st_size;
  }
  return 0; //Is this ok?
}

/* ---------- Read -----------------------------------------------------*/

/* ---------- OpenRIFF --------------------------------------------------*/
IFFP OpenRIFF(FILE *file, GroupContext *new, ClientFrame *clientFrame) {
  IFFP iffp = IFF_OKAY;

  new->parent       = NULL;		/* "whole file" has no parent.*/
  new->clientFrame  = clientFrame;
  new->file         = file;
  new->position     = 0;
  new->ckHdr.ckID   = new->subtype    = NULL_CHUNK;
  new->ckHdr.ckSize = new->bytesSoFar = 0;

  /* Set new->bound and go to the file's beginning. */
  new->bound = FileLength(file);
  if (new->bound < 0)
    iffp = new->bound;		   /* File system error! */
  else if ( new->bound < sizeof(ChunkHeader) )
    iffp = NOT_IFF;			   /* Too small for an IFF file. */
  else
    fseek(file, 0, SEEK_SET);  /* Go to file start. */

  return(iffp);
}

/* ---------- OpenRGroup -----------------------------------------------*/
IFFP OpenRGroup(GroupContext *parent, GroupContext *new) {
  IFFP iffp = IFF_OKAY;

  new->parent       = parent;
  new->clientFrame  = parent->clientFrame;
  new->file         = parent->file;
  new->position     = parent->position;
  new->bound        = parent->position + ChunkMoreBytes(parent);
  new->ckHdr.ckID   = new->subtype    = NULL_CHUNK;
  new->ckHdr.ckSize = new->bytesSoFar = 0;

  if ( new->bound > parent->bound  ||  IS_ODD(new->bound) )
    iffp = BAD_IFF;
  return(iffp);
}

/* ---------- CloseRGroup -----------------------------------------------*/
IFFP CloseRGroup(GroupContext *context) {
  register int32_t position;

  if (context->parent == NULL) {
  }  /* Context for whole file.*/
  else {
    position = context->position;
    context->parent->bytesSoFar += position - context->parent->position;
    context->parent->position = position;
  }
  return(IFF_OKAY);
}

/* ---------- SkipFwd --------------------------------------------------*/
/* Skip over bytes in a context. Won't go backwards.*/
/* Updates context->position but not context->bytesSoFar.*/
/* This implementation is AmigaDOS specific.*/
IFFP SkipFwd(GroupContext *context, int32_t bytes) {
  IFFP iffp = IFF_OKAY;

  if (bytes > 0) {
    if (-1 == fseek(context->file, bytes, SEEK_CUR))
      iffp = BAD_IFF;	/* Ran out of bytes before chunk complete.*/
    else
      context->position += bytes;
  }
  return(iffp);
}

/* ---------- GetChunkHdr ----------------------------------------------*/
ID GetChunkHdr(GroupContext *context) {
  register IFFP iffp;
  int32_t remaining;

  /* Skip remainder of previous chunk & padding. */
  iffp = SkipFwd(context,
		 ChunkMoreBytes(context) + IS_ODD(context->ckHdr.ckSize));
  //printf("GetChunkHdr@%p: iffp = %d $%X\n", &GetChunkHdr, iffp, iffp);
  CheckIFFP();

  /* Set up to read the new header. */
  context->ckHdr.ckID = BAD_IFF;	/* Until we know it's okay, mark it BAD.*/
  context->subtype    = NULL_CHUNK;
  context->bytesSoFar = 0;

  /* Generate a psuedo-chunk if at end-of-context. */
  remaining = context->bound - context->position;
  if (remaining == 0) {
    context->ckHdr.ckSize = 0;
    context->ckHdr.ckID   = END_MARK;
  }

  /* BAD_IFF if not enough bytes in the context for a ChunkHeader.*/
  else if (sizeof(ChunkHeader) > remaining) {
    context->ckHdr.ckSize = remaining;
  }

  /* Read the chunk header (finally). */
  else {
    switch (
	    fread(&context->ckHdr, sizeof(ChunkHeader), 1, context->file)
	    ) {
    case -1: return(context->ckHdr.ckID = DOS_ERROR);
    case 0:  return(context->ckHdr.ckID = BAD_IFF);
    default: //Bytes read, now take care of endianess issues
      context->ckHdr.ckID = ntohl(context->ckHdr.ckID);
      context->ckHdr.ckSize = ntohl(context->ckHdr.ckSize);
    }

    /* Check: Top level chunk must be LIST or FORM or CAT. */
    if (context->parent == NULL)
      switch(context->ckHdr.ckID) {
      case FORM:  case LIST:  case CAT:  break;
      default:    return(context->ckHdr.ckID = NOT_IFF);
      }

    /* Update the context. */
    context->position += sizeof(ChunkHeader);
    remaining         -= sizeof(ChunkHeader);

    /* Non-positive ID values are illegal and used for error codes.*/
    /* We could check for other illegal IDs...*/
    if (context->ckHdr.ckID <= 0)
      context->ckHdr.ckID = BAD_IFF;

    /* Check: ckSize negative or larger than # bytes left in context? */
    else if (context->ckHdr.ckSize < 0  ||
	     context->ckHdr.ckSize > remaining) {
      context->ckHdr.ckSize = remaining;
      context->ckHdr.ckID   = BAD_IFF;
    }

    /* Automatically read the LIST, FORM, PROP, or CAT subtype ID */
    else switch (context->ckHdr.ckID) {
      case LIST:  case FORM:  case PROP:  case CAT:  {
	iffp = IFFReadBytes(context,
			    (char *)&context->subtype,
			    sizeof(ID));
	if (iffp != IFF_OKAY)
	  context->ckHdr.ckID = iffp;
	//Again have to handle endianess
	context->subtype = ntohl(context->subtype);
	break; }
      }

  }
  return(context->ckHdr.ckID);
}

/* ---------- IFFReadBytes ---------------------------------------------*/
IFFP IFFReadBytes(GroupContext *context, void *buffer, int32_t nBytes) {
  register IFFP iffp = IFF_OKAY;
  
  if (nBytes < 0)
    iffp = CLIENT_ERROR;
  else if (nBytes > ChunkMoreBytes(context))
    iffp = SHORT_CHUNK;
  else if (nBytes > 0)
    switch ( fread(buffer, nBytes, 1, context->file) ) {
    case -1:
      iffp = DOS_ERROR;
      break;
    case 0:
      iffp = BAD_IFF;
      break;
    default:
      context->position   += nBytes;
      context->bytesSoFar += nBytes;
    }
  
  return(iffp);
}

/* ---------- SkipGroup ------------------------------------------------*/
IFFP SkipGroup(GroupContext *context) {
  return IFF_OKAY;
}	/* Nothing to do, thanks to GetChunkHdr */

/* ---------- ReadIFF --------------------------------------------------*/
IFFP ReadIFF(FILE *file, ClientFrame *clientFrame) {
  IFFP iffp;
  GroupContext context;

  iffp = OpenRIFF(file, &context, clientFrame);
  context.clientFrame = clientFrame;
  //printf("ReadIFF@%p: iffp = %d $%X\n", &ReadIFF, iffp, iffp);
  
  if (iffp == IFF_OKAY) {
    iffp = GetChunkHdr(&context);
    //printf("ReadIFF@%p: iffp = %d $%X\n", &ReadIFF, iffp, iffp);
    switch (iffp) {
    case FORM:
      //printf("Going for FORM!\n");
      iffp = (*clientFrame->getForm)(&context);
      break;
    case LIST:
      iffp = (*clientFrame->getList)(&context);
      break;
    case CAT :
      iffp = (*clientFrame->getCat )(&context);
      break;
      /* default: Includes IFF_DONE, BAD_IFF, NOT_IFF... */
    }
  }
  CloseRGroup(&context);
  
  if (iffp > 0)		/* Make sure we don't return an ID.*/
    iffp = NOT_IFF;		/* GetChunkHdr should've caught this.*/
  return(iffp);
}

/* ---------- ReadIList ------------------------------------------------*/
IFFP ReadIList(GroupContext *parent, ClientFrame *clientFrame) {
  GroupContext listContext;
  IFFP iffp;
  BOOL propOk = TRUE;

  iffp = OpenRGroup(parent, &listContext);
  CheckIFFP();
  
  /* One special case test lets us handle CATs as well as LISTs.*/
  if (parent->ckHdr.ckID == CAT)
    propOk = FALSE;
  else
    listContext.clientFrame = clientFrame;
  
  do {
    switch (iffp = GetChunkHdr(&listContext)) {
    case PROP:
      if (propOk)
	iffp = (*clientFrame->getProp)(&listContext);
      else
	iffp = BAD_IFF;
      break;
    case FORM:
      iffp = (*clientFrame->getForm)(&listContext);
      break;
    case LIST:
      iffp = (*clientFrame->getList)(&listContext);
      break;
    case CAT :
      iffp = (*clientFrame->getCat )(&listContext);
      break;
      /* default: Includes END_MARK, IFF_DONE, BAD_IFF, NOT_IFF... */
    }
    if (listContext.ckHdr.ckID != PROP)
      propOk = FALSE;	/* No PROPs allowed after this point.*/
  } while (iffp == IFF_OKAY);

  CloseRGroup(&listContext);

  if (iffp > 0)	/* Only chunk types above are allowed in a LIST/CAT.*/
    iffp = BAD_IFF;
  return(iffp == END_MARK ? IFF_OKAY : iffp);
}

/* ---------- ReadICat -------------------------------------------------*/
/* By special arrangement with the ReadIList implement'n, this is trivial.*/
IFFP ReadICat(GroupContext *parent) {
  return( ReadIList(parent, NULL) );
}

/* ---------- GetFChunkHdr ---------------------------------------------*/
ID GetFChunkHdr(GroupContext *context) {
  register ID id;

  id = GetChunkHdr(context);
  if (id == PROP)
    context->ckHdr.ckID = id = BAD_IFF;
  return(id);
}

/* ---------- GetF1ChunkHdr ---------------------------------------------*/
ID GetF1ChunkHdr(GroupContext *context) {
  register ID id;
  register ClientFrame *clientFrame = context->clientFrame;
  
  switch (id = GetChunkHdr(context)) {
  case PROP:
    id = BAD_IFF;
    break;
  case FORM:
    id = (*clientFrame->getForm)(context);
    break;
  case LIST:
    id = (*clientFrame->getList)(context);
    break;
  case CAT :
    id = (*clientFrame->getCat )(context);
    break;
    /* Default: let the caller handle other chunks */
  }
  return(context->ckHdr.ckID = id);
}

/* ---------- GetPChunkHdr ---------------------------------------------*/
ID GetPChunkHdr(GroupContext *context) {
  register ID id;
  
  id = GetChunkHdr(context);
  switch (id) {
  case LIST:
  case FORM:
  case PROP:
  case CAT:
    id = context->ckHdr.ckID = BAD_IFF;
    break;
  }
  return(id);
}


